/*   Copyright (C) 2008  Jeroen v. Schagen & Youri Lima
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace Engine.Math
{
    /// <summary>
    ///     Represents a vector in a three-dimensional space.
    /// 
    ///     The direction in a three-dimensional space formalised as 
    ///     distances along the 3 orthoganal axes (x, y, z).
    /// </summary>
    public struct Vector3
    {
        public float x;
        public float y;
        public float z;

        # region Constant

        /// <summary>
        ///     The representation of an empty three-dimensional vector.
        /// </summary>
        public static Vector3 ZERO
        {
            get { return new Vector3(0, 0, 0); }
        }

        /// <summary>
        ///     The representation of a three-dimensional one vector.
        /// 
        ///     In the one vector all axis are valued at one.
        /// </summary>
        public static Vector3 ONE
        {
            get { return new Vector3(1, 1, 1); }
        }

        /// <summary>
        ///     The representation of a three-dimensional x-unit vector.
        /// 
        ///     In the x-unit vector, the x axis is valued at one,
        ///     all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_X
        {
            get { return new Vector3(1, 0, 0); }
        }

        /// <summary>
        ///     The representation of a three-dimensional y-unit vector.
        /// 
        ///     In the y-unit vector, the y axis is valued at one,
        ///     all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_Y
        {
            get { return new Vector3(0, 1, 0); }
        }

        /// <summary>
        ///     The representation of a three-dimensional z-unit vector.
        /// 
        ///     In the z-unit vector, the z axis is valued at one,
        ///     all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_Z
        {
            get { return new Vector3(0, 0, 1); }
        }

        /// <summary>
        ///     The representation of a three-dimensional negative 
        ///     x-unit vector.
        /// 
        ///     In the negative x-unit vector, the x axis is valued 
        ///     at minus one, all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_NEGATIVE_X
        {
            get { return new Vector3(-1, 0, 0); }
        }

        /// <summary>
        ///     The representation of a three-dimensional negative 
        ///     y-unit vector.
        /// 
        ///     In the negative y-unit vector, the y axis is valued 
        ///     at minus one, all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_NEGATIVE_Y
        {
            get { return new Vector3(0, -1, 0); }
        }

        /// <summary>
        ///     The representation of a three-dimensional negative 
        ///     z-unit vector.
        /// 
        ///     In the negative z-unit vector, the z axis is valued 
        ///     at minus one, all other values are set at zero.
        /// </summary>
        public static Vector3 UNIT_NEGATIVE_Z
        {
            get { return new Vector3(0, 0, -1); }
        }

        # endregion


        # region Constructor

        /// <summary>
        ///     Construct vector using float values.
        /// </summary>
        /// 
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="z">Z</param>
        public Vector3(float x, float y, float z)
        {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        /// <summary>
        ///     Construct vector using a vector object.
        /// </summary>
        /// 
        /// <param name="v">Vector</param>
        public Vector3(Vector3 v)
        {
            this.x = v.x;
            this.y = v.y;
            this.z = v.z;
        }

        # endregion


        # region Function

        /// <summary>
        ///     Calculates the cross product of this and a given vector.
        /// 
        ///     In mathematics, the cross product is a binary operation on two vectors in a 
        ///     three-dimensional Euclidean space that results in another vector which is 
        ///     perpendicular to the two input vectors.
        /// 
        ///     Formula: a * b = (a2b3 - a3b2), (a3b1 - a1b3), (a1b2 - a2b1)
        /// </summary>
        /// 
        /// <param name="v"></param>
        /// <returns></returns>
        public Vector3 CrossProduct(Vector3 v)
        {
            return new Vector3( y * v.z - z * v.y,
                             z * v.x - x * v.z,
                             x * v.y - y * v.x );
        }

        /// <summary>
        ///     Calculates distance to destined position.
        /// </summary>
        /// 
        /// <param name="v"></param>
        /// <returns></returns>
        public float Distance(Vector3 v)
        {
            return (this - v).Length;
        }

        /// <summary>
        ///     Calculates the dot product of this and a given vector.
        /// 
        ///     In mathematics, the dot product, also known as the scalar product, is an 
        ///     operation which takes two vectors over the float numbers R and returns a 
        ///     float-valued scalar quantity. 
        /// 
        ///     It is the standard inner product of the Euclidean space.
        /// 
        ///     Formula: a * b = a1b1 + a2b2 = a3b3
        /// </summary>
        /// 
        /// <param name="v"></param>
        /// <returns></returns>
        public float DotProduct(Vector3 v)
        {
            return (x * v.x) + (y * v.y) + (z * v.z);
        }

        /// <summary>
        ///     Compares this object to parametered object.
        /// </summary>
        /// 
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            if (obj.GetType() == typeof(Vector3))
            {
                // Cast object to vector3
                Vector3 vector = (Vector3) obj;

                // Return result
                return (x == vector.x) && (y == vector.y) && (z == vector.z);
            }

            return false;
        }

        /// <summary>
        ///     Retrieves the hashcode of current object.
        /// </summary>
        /// 
        /// <returns></returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        ///     Validate that the vector is larger then zero.
        /// </summary>
        public bool IsZeroLength()
        {
            return (this.SquareLength() < (1e-06 * 1e-06));
        }

        /// <summary>
        ///     Calculate length of vector.
        /// 
        ///     The length or magnitude or norm of the vector a is denoted by ||a|| or, 
        ///     less commonly, |a|, which is not to be confused with the absolute value 
        ///     (a scalar "norm"). 
        /// 
        ///     The length of the vector a = a1e1 + a2e2+ a3e3 in a three-dimensional 
        ///     Euclidean space, where e1, e2, e3 are orthogonal unit vectors, can be 
        ///     computed with the Euclidean norm which is a consequence of the Pythagorean 
        ///     theorem since the basis vectors e1, e2, e3 are orthogonal unit vectors. 
        /// 
        ///     This happens to be equal to the square root of the dot product of the vector 
        ///     with itself.
        /// 
        ///     Warning: This function takes up allot of performance.
        /// </summary>
        public float Length
        {
            get { return (float) System.Math.Sqrt((double) this.SquareLength()); }
        }

        /// <summary>
        ///     Normalises this vector.
        /// </summary>
        /// 
        /// <returns></returns>
        public float Normalise()
        {
            float length = this.Length;

            // Will also work for zero-sized vectors, but will change nothing
            if (length > 1e-08)
            {
                float ilength = 1 / length;

                x *= ilength;
                y *= ilength;
                z *= ilength;
            }

            return length;
        }

        /// <summary>
        ///     Calculates inverse vector of current position to destined position.
        /// </summary>
        /// 
        /// <param name="v"></param>
        /// <returns></returns>
        public Vector3 Reflect(Vector3 v)
        {
            return new Vector3(this - (v * (DotProduct(v) * 2.0f)));
        }

        /// <summary>
        ///     Calculates the needed rotation to face the given position.
        /// </summary>
        /// 
        /// <param name="v"></param>
        /// <returns></returns>
        public Quaternion Rotation(Vector3 v)
        {
            Quaternion q = Quaternion.IDENTITY;

            // Clone current vector
            Vector3 clone = new Vector3(this);

            // Normalize vectors
            clone.Normalise();
            v.Normalise();

            // Dot product
            float d = clone.DotProduct(v);

            // When dot product equals one, the vectors are equal
            if (d >= 1.0f)
            {
                return q;
            }

            if (d < (1e-6f - 1.0f))
            {
                // Generate axis
                Vector3 axis = Vector3.UNIT_X.CrossProduct(this);

                if (axis.IsZeroLength())
                {
                    // Pick another axis if colinear
                    axis = Vector3.UNIT_Y.CrossProduct(this);
                }

                // Normalise axis
                axis.Normalise();

                // Apply axis to quaternion
                q = Quaternion.FromAngleAxis(new Radian((float)System.Math.PI), axis);
            }

            else
            {
                float s = (float)System.Math.Sqrt((1 + d) * 2);
                float invs = 1 / s;

                Vector3 c = clone.CrossProduct(v);

                q.x = c.x * invs;
                q.y = c.y * invs;
                q.z = c.z * invs;
                q.w = (float)(s * 0.5);

                q.Normalise();
            }

            return q;
        }

        /// <summary>
        ///     Calculate square length of vector (length before applying square root). 
        ///     
        ///     This can be used over length to compare distances, since it is way 
        ///     more performance efficient.
        /// </summary>
        public float SquareLength()
        {
            return (x * x) + (y * y) + (z * z);
        }

        /// <summary>
        ///     In mathematics, a unit vector in a normed vector space is a 
        ///     vector (often a spatial vector) whose length is 1 (the unit length).
        /// </summary>
        public Vector3 Unit
        {
            get
            {
                // Create copy of this vector
                Vector3 r = new Vector3(this);

                // Normalise the vector
                r.Normalise();

                // Return the unit vector
                return r;
            }
        }

        # endregion


        # region Operator

        // Arithmetic Operators
        public static Vector3 operator +(Vector3 v1, Vector3 v2)
        {
            return new Vector3( v1.x + v2.x,
                                v1.y + v2.y,
                                v1.z + v2.z );
        }

        public static Vector3 operator -(Vector3 v1, Vector3 v2)
        {
            return new Vector3( v1.x - v2.x,
                                v1.y - v2.y,
                                v1.z - v2.z );
        }

        public static Vector3 operator *(Vector3 v1, Vector3 v2)
        {
            return new Vector3( v1.x * v2.x,
                                v1.y * v2.y,
                                v1.z * v2.z );
        }

        public static Vector3 operator /(Vector3 v1, Vector3 v2)
        {
            return new Vector3( v1.x / v2.x,
                                v1.y / v2.y,
                                v1.z / v2.z );
        }

        public static Vector3 operator %(Vector3 v1, Vector3 v2)
        {
            return new Vector3( v1.x % v2.x,
                                v1.y % v2.y,
                                v1.z % v2.z );
        }

        public static Vector3 operator ++(Vector3 v)
        {
            return new Vector3( v.x + 1,
                                v.y + 1,
                                v.z + 1 );
        }

        public static Vector3 operator --(Vector3 v)
        {
            return new Vector3( v.x - 1,
                                v.y - 1,
                                v.z - 1 );
        }

        public static Vector3 operator -(Vector3 v)
        {
            return new Vector3( -v.x,
                                -v.y,
                                -v.z );
        }

        // Comparison Operators
        public static bool operator ==(Vector3 v1, Vector3 v2)
        {
            return (v1.x == v2.x) && (v1.y == v2.y) && (v1.z == v2.z);
        }

        public static bool operator !=(Vector3 v1, Vector3 v2)
        {
            return (v1.x != v2.x) || (v1.y != v2.y) || (v1.z != v2.z);
        }

        public static bool operator >(Vector3 v1, Vector3 v2)
        {
            return v1.SquareLength() > v2.SquareLength();
        }

        public static bool operator <(Vector3 v1, Vector3 v2)
        {
            return v1.SquareLength() < v2.SquareLength();
        }

        public static bool operator >=(Vector3 v1, Vector3 v2)
        {
            return v1.SquareLength() >= v2.SquareLength();
        }

        public static bool operator <=(Vector3 v1, Vector3 v2)
        {
            return v1.SquareLength() <= v2.SquareLength();
        }

        # endregion


        # region Cross-class operator

        // Float
        public static Vector3 operator +(Vector3 v, float value)
        {
            return new Vector3( v.x + value,
                                v.y + value,
                                v.z + value );
        }

        public static Vector3 operator +(float value, Vector3 v)
        {
            return new Vector3( v.x + value,
                                v.y + value,
                                v.z + value );
        }

        public static Vector3 operator -(Vector3 v, float value)
        {
            return new Vector3( v.x - value,
                                v.y - value,
                                v.z - value );
        }

        public static Vector3 operator -(float value, Vector3 v)
        {
            return new Vector3(v.x - value,
                                v.y - value,
                                v.z - value);
        }

        public static Vector3 operator *(Vector3 v, float value)
        {
            return new Vector3( v.x * value,
                                v.y * value,
                                v.z * value );
        }

        public static Vector3 operator *(float value, Vector3 v)
        {
            return new Vector3( v.x * value,
                                v.y * value,
                                v.z * value );
        }

        public static Vector3 operator /(Vector3 v, float value)
        {
            return new Vector3( v.x / value,
                                v.y / value,
                                v.z / value );
        }

        public static Vector3 operator /(float value, Vector3 v)
        {
            return new Vector3( v.x / value,
                                v.y / value,
                                v.z / value );
        }

        // Quaternion
        public static Vector3 operator *(Vector3 v, Quaternion q)
        {
            // Initiate vectors
            Vector3 tv = Vector3.ZERO;
            Vector3 ttv = Vector3.ZERO;
            Vector3 qv = new Vector3(q.x, q.y, q.z);

            // Cross product
            tv = qv.CrossProduct(v);
            ttv = qv.CrossProduct(tv);

            // Multiply with w
            tv = tv * (q.w * 2.0f);
            ttv = ttv * 2.0f;

            // Add up values
            return v + tv + ttv;
        }

        # endregion

        public override string ToString()
        {
            return x.ToString() + ", " + y.ToString() + ", " + z.ToString();
        }
    }
}
